import asyncore
import socket
from S3D.Net import ReadBuffer, Unpacker
import traceback
import io
import os
from S3D.IO import walk_files
import imp

CMD_UPDATE      = 1
CMD_USER        = 2

CACHE_PATH      = ".\\TerminalCache"

def _call(obj, name, *args, **kw):
    if not hasattr(obj, name):
        return

    try:
        proc = getattr(obj, name)
        return proc(*args, **kw)
    except:
        traceback.print_exc()
        return None

class TerminalServer(asyncore.dispatcher):
    def __init__(self, port):
        super(TerminalServer, self).__init__()

        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.bind(("", port))
        self.listen(1)
        
        self.loadModules()

    def loadModules(self):
        if not os.path.exists(CACHE_PATH):
            os.makedirs(CACHE_PATH)

        self.modules = {}

        for root, name in walk_files(CACHE_PATH, ".py", lambda root, name: (root, os.path.splitext(name)[0])):
            self.reloadModule(name)

    def reloadModule(self, name):
        res = imp.find_module(name, [CACHE_PATH])
        if not res:
            return

        m = imp.load_module(name, *res)
        self.modules[name] = m

        _call(m, "register", self)

    def handle_accept(self):
        conn, addr = self.accept()

        processor = TerminalProcessor(self, conn, addr)

        for m in self.modules.values():
            _call(m, "connected", processor, addr)

class TerminalProcessor(asyncore.dispatcher_with_send):
    def __init__(self, server, conn, addr):
        super(TerminalProcessor, self).__init__(conn)
        self.server = server

        print("Connected:", addr)
        self.addr = addr
        self.readBuffer     = ReadBuffer()
        self.pendingMessage = -1
        self.writable       = False

    def setWritable(self, value):
        self.writable = value

    def writable(self):
        if self.writable:
            return True

        parent = super(TerminalProcessor, self)
        return parent.writable()

    def handle_write(self):
        for m in self.modules.values():
            _call(m, "handle_write", self)

        parent = super(TerminalProcessor, self)
        if parent.writable():
            parent.handle_write()

    def handle_read(self):
        data = self.recv(4096)
        self.readBuffer.append(data)

        while True:
            if self.pendingMessage == -1:
                if self.readBuffer.getSize() < 4:
                    return

                self.pendingMessage = self.readInt()

            if self.prendingMessage > self.readBuffer.getSize():
                return

            msg = self.readBuffer.read(self.pendingMessage)
            self.pendingMessage = -1

            try:
                self.processMessage(msg)
            except:
                traceback.print_exc()

    def processMessage(self, msg):
        stream = io.BytesIO(msg)
        input  = Unpacker(stream)

        cmd = input.readShort()
        if cmd == CMD_UPDATE:
            moduleName  = input.readStr()
            size        = input.readInt()
            data        = input.readData(size)

            self.updateModule(moduleName, data)

        elif cmd == CMD_USER:
            mark        = input.readStr()

            self.processUserMessage(mark, stream)

    def updateModule(self, name, data):
        filename = os.path.join(CACHE_PATH, name + ".py")
        f = open(filename, "wb")
        f.write(data)
        f.close()

        self.server.reloadModule(name)

    def processUserMessage(self, mark, stream):
        offset = stream.tell()

        for p in self.modules.values():
            stream.seek(offset)
            _call(p, "handle_message", mark, input)
            
server = TerminalServer(50013)
TerminalProcessor(server, None, None)